#include <QtCore/QTimer>
#include <QtCore/QMutexLocker>
#include <QtSql/QSqlError>
#include <QtSql/QSqlRecord>
#include "dbmanager/database_manager_p.h"
#include "dbmanager/query_handler.h"
#include "dbmanager/sql_query_p.h"

namespace dbmanager {

SqlQueryPrivate::SqlQueryPrivate(bool return_result, bool use_transaction) :
  success(false),
  use_transaction(use_transaction),
  return_result(return_result),
  fault_code(-1) {

}

SqlQueryPrivate::~SqlQueryPrivate() {
  queries.clear();
  qDeleteAll(results.begin(), results.end());
  results.clear();
}

SqlQuery::SqlQuery(bool return_result, bool use_transaction) :
  p(new SqlQueryPrivate(return_result, use_transaction)) {

}

SqlQuery::~SqlQuery() {
  if(p) {
    delete p;
    p = 0;
  }
}

void SqlQuery::add(const QString &query) {
  p->queries.push_back(query);
}

bool SqlQuery::success() const {
  return p->success;
}

bool SqlQuery::returnResult() const {
  return p->return_result;
}

bool SqlQuery::useTransaction() const {
  return p->use_transaction;
}

int SqlQuery::getFaultCode() const {
  return p->fault_code;
}

QString SqlQuery::getFaultString() const {
  return p->fault_string;
}

QList<QString> SqlQuery::queries() const {
  return p->queries;
}

QList<QSqlRecord *> SqlQuery::results() const {
  return p->results;
}

QueryHandler::QueryHandler(int id, DatabaseManagerPrivate *parent) :
  database(0),
  parent(parent),
  db_name(parent->name_prefix + QString("_%1").arg(id)) {
  moveToThread(this);
  connect(parent, SIGNAL(newQuery()),
          this, SLOT(execNextQuery()), Qt::QueuedConnection);
  connect(this, SIGNAL(started()), this, SLOT(threadStarted()));
  connect(this, SIGNAL(finished()), this, SLOT(threadStoped()));
  connect(this, SIGNAL(terminated()), this, SLOT(threadStoped()));
}

QueryHandler::~QueryHandler() {

}

void QueryHandler::threadStarted() {
  createDatabase();
}

void QueryHandler::threadStoped() {
  destroyDatabase();
}

void QueryHandler::execNextQuery() {
  parent->mutex.lock();
  if(parent->queries.isEmpty() == false) {
    SqlQuery *query = parent->queries.takeFirst();
    parent->mutex.unlock();

    exec(query);

    while(query->success() == false &&
          (query->getFaultCode() == 1205 ||
           query->getFaultCode() == 1206 ||
           query->getFaultCode() == 1213 ||
           query->getFaultCode() == 1412 ||
           query->getFaultCode() == 2006)) {
      database->close();
      exec(query);
    }

    if(query->success() == false) {
      database->close();
    }

    emit query->executed();
  } else {
    parent->mutex.unlock();
  }
}

void QueryHandler::createDatabase() {
  database = new QSqlDatabase(QSqlDatabase::addDatabase("QMYSQL", db_name));

  database->setHostName(parent->db_host);
  database->setPort(parent->db_port);
  database->setDatabaseName(parent->db_name);
  database->setUserName(parent->db_user);
  database->setPassword(parent->db_pass);
}

void QueryHandler::destroyDatabase() {
  if(database) {
    if(database->isOpen()) {
      database->close();
    }

    delete database;
    database = 0;

    QSqlDatabase::removeDatabase(db_name);
  }
}

bool QueryHandler::checkAndOpenDb() {
  bool res(database->isOpenError());
  if(res || !database->isOpen()) {
    database->close();
    res = database->open();
  } else {
    res = true;
  }

  return res;
}

void QueryHandler::exec(SqlQuery *query) {
  if(checkAndOpenDb()) {
    if(query->p->use_transaction) {
      if(!database->transaction()) {
        query->p->success = false;
        query->p->fault_code = database->lastError().number();
        query->p->fault_string = database->lastError().text();
        parent->faultExecute(query->p->fault_code);
        return;
      }
    }

    QSqlQuery *q = new QSqlQuery(*database);

    for(int i = 0; i < query->p->queries.size(); i++) {
      if(q->exec(query->p->queries[i])) {
        if(q->isActive()) {
          if(query->p->return_result) {
            while(q->next()) {
              query->p->results.push_back(new QSqlRecord(q->record()));
            }
          }
          q->clear();
        } else {
          query->p->success = false;
          query->p->fault_code = q->lastError().number();
          query->p->fault_string = q->lastError().text();
          parent->faultExecute(query->p->fault_code);
          if(query->p->use_transaction) {
            database->rollback();
          }
          delete q;
          return;
        }
      } else {
        query->p->success = false;
        query->p->fault_code = q->lastError().number();
        query->p->fault_string = q->lastError().text();
        parent->faultExecute(query->p->fault_code);
        if(query->p->use_transaction) {
          database->rollback();
        }
        delete q;
        return;
      }
    }

    delete q;
  } else {
    query->p->success = false;
    query->p->fault_code = database->lastError().number();
    query->p->fault_string = database->lastError().text();
    parent->faultExecute(query->p->fault_code);
    return;
  }

  if(query->p->use_transaction) {
    if(database->commit()) {
      query->p->success = true;
      parent->mutex.lock();
      parent->executed_success++;
      parent->mutex.unlock();
    } else {
      query->p->success = false;
      query->p->fault_code = database->lastError().number();
      query->p->fault_string = database->lastError().text();
      parent->faultExecute(query->p->fault_code);
    }
  } else {
    query->p->success = true;
    parent->mutex.lock();
    parent->executed_success++;
    parent->mutex.unlock();
  }
}

DatabaseManagerPrivate::DatabaseManagerPrivate(const QString &db_host,
                                               quint16 db_port,
                                               const QString &db_name,
                                               const QString &db_user,
                                               const QString &db_pass,
                                               int num_connect_db,
                                               const QString &name_prefix) :
  executed_success(0),
  db_host(db_host),
  db_port(db_port),
  db_name(db_name),
  db_user(db_user),
  db_pass(db_pass),
  num_connect_db(num_connect_db),
  name_prefix(name_prefix) {
  for(int i = 0; i < num_connect_db; i++) {
    QueryHandler *handler = new QueryHandler(i, this);

    handlers.push_back(handler);

    handler->start();
  }
}

DatabaseManagerPrivate::~DatabaseManagerPrivate() {
  for(int i = 0; i < handlers.size(); i++) {
    QueryHandler *handler = handlers[i];

    if(handler->isRunning()) {
      handler->quit();
      handler->wait();
    }

    delete handler;
  }
}

void DatabaseManagerPrivate::faultExecute(int code) {
  mutex.lock();
  if(executed_fault.contains(code)) {
    executed_fault[code]++;
  } else {
    executed_fault.insert(code, 1);
  }
  mutex.unlock();
}

DatabaseManager::DatabaseManager(const QString &db_host,
                                 quint16 db_port,
                                 const QString &db_name,
                                 const QString &db_user,
                                 const QString &db_pass,
                                 int num_connect_db,
                                 const QString &name_prefix) :
  p(new DatabaseManagerPrivate(db_host,
                               db_port,
                               db_name,
                               db_user,
                               db_pass,
                               num_connect_db,
                               name_prefix)) {

}

DatabaseManager::~DatabaseManager() {
  if(p) {
    delete p;
    p = 0;
  }
}

int DatabaseManager::execSuccess() const {
  QMutexLocker locker(&p->mutex);
  return p->executed_success;
}

QHash<int, int> DatabaseManager::execFault() const {
  QMutexLocker locker(&p->mutex);
  return p->executed_fault;
}

void DatabaseManager::execute(SqlQuery *query) {
  p->mutex.lock();
  p->queries.push_back(query);
  p->mutex.unlock();

  emit p->newQuery();
}

QString DatabaseManager::getDbHost() const {
  return p->db_host;
}

quint16 DatabaseManager::getDbPort() const {
  return p->db_port;
}

QString DatabaseManager::getDbName() const {
  return p->db_name;
}

QString DatabaseManager::getDbUser() const {
  return p->db_user;
}

QString DatabaseManager::getDbPass() const {
  return p->db_pass;
}

int DatabaseManager::getNumConnectDb() const {
  return p->num_connect_db;
}

QString DatabaseManager::getNamePrefix() const {
  return p->name_prefix;
}

} // namespace dbmanager
